package org.domeos.framework.api.service.storage.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.domeos.basemodel.HttpResponseTemp;
import org.domeos.basemodel.ResultStat;
import org.domeos.exception.K8sDriverException;
import org.domeos.framework.api.biz.OperationHistory;
import org.domeos.framework.api.biz.cluster.ClusterBiz;
import org.domeos.framework.api.biz.collection.CollectionBiz;
import org.domeos.framework.api.biz.deployment.DeploymentBiz;
import org.domeos.framework.api.biz.deployment.VersionBiz;
import org.domeos.framework.api.biz.storage.StorageBiz;
import org.domeos.framework.api.controller.exception.ApiException;
import org.domeos.framework.api.model.auth.User;
import org.domeos.framework.api.model.auth.related.Role;
import org.domeos.framework.api.model.cluster.Cluster;
import org.domeos.framework.api.model.collection.CollectionAuthorityMap;
import org.domeos.framework.api.model.collection.CollectionResourceMap;
import org.domeos.framework.api.model.collection.related.ResourceType;
import org.domeos.framework.api.model.deployment.Deployment;
import org.domeos.framework.api.model.deployment.Version;
import org.domeos.framework.api.model.operation.OperationRecord;
import org.domeos.framework.api.model.operation.OperationType;
import org.domeos.framework.api.model.storage.DeployInstance;
import org.domeos.framework.api.model.storage.Storage;
import org.domeos.framework.api.model.storage.StorageInfo;
import org.domeos.framework.api.model.storage.VolumeDeployMap;
import org.domeos.framework.api.service.storage.StorageService;
import org.domeos.framework.engine.AuthUtil;
import org.domeos.framework.engine.exception.DriverException;
import org.domeos.framework.engine.k8s.util.Fabric8KubeUtils;
import org.domeos.framework.engine.k8s.util.KubeUtils;
import org.domeos.global.CurrentThreadInfo;
import org.domeos.global.GlobalConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import io.fabric8.kubernetes.api.model.LabelSelector;
import io.fabric8.kubernetes.api.model.NFSVolumeSource;
import io.fabric8.kubernetes.api.model.PersistentVolume;
import io.fabric8.kubernetes.api.model.PersistentVolumeBuilder;
import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
import io.fabric8.kubernetes.api.model.PersistentVolumeClaimBuilder;
import io.fabric8.kubernetes.api.model.PersistentVolumeClaimSpec;
import io.fabric8.kubernetes.api.model.PersistentVolumeSpec;
import io.fabric8.kubernetes.api.model.ResourceRequirements;

@Service
public class StorageServiceImpl implements StorageService{

	@Autowired
	StorageBiz storageBiz;
	
	@Autowired
    CollectionBiz collectionBiz;
	
	@Autowired
    OperationHistory operationHistory;
	
	@Autowired
	ClusterBiz clusterBiz;
	
	@Autowired
	DeploymentBiz deploymentBiz;
	
	@Autowired
	VersionBiz versionBiz;
	
	private final ResourceType resourceType = ResourceType.STORAGE_CLUSTER;
	
	@Override
	public HttpResponseTemp<?> setStorage(Storage storage) {
		if(storage == null) {
			return ResultStat.PARAM_ERROR.wrap("参数不能为空");
		}
		Storage isExist = storageBiz.getStorageByName(storage.getName());
		if(isExist != null) {
			return ResultStat.PARAM_ERROR.wrap("名称已经存在");
		}
		
		List<Storage> error = storageBiz.getStorageByPathAndServer(storage.getPath(), storage.getServer());
		if(error != null && error.size() > 0) {
			return ResultStat.PARAM_ERROR.wrap("路径和服务器地址重复");
		}
		storage.setCreateTime(System.currentTimeMillis());
		storageBiz.createStorage(storage);
		try {
			createPVAndPVC(storage);
		} catch (DriverException | K8sDriverException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		CollectionAuthorityMap collectionAuthorityMap = new CollectionAuthorityMap(storage.getId(),
                resourceType,
                CurrentThreadInfo.getUserId(),
                Role.MASTER,
                System.currentTimeMillis()
                );
        collectionBiz.addAuthority(collectionAuthorityMap);
        operationHistory.insertRecord(new OperationRecord(
        		storage.getId(),
                resourceType,
                OperationType.SET,
                CurrentThreadInfo.getUserId(),
                CurrentThreadInfo.getUserName(),
                "OK",
                "",
                System.currentTimeMillis()
        ));
		return ResultStat.OK.wrap(storage);
	}
	
	private void createPVAndPVC(Storage storage) throws DriverException, K8sDriverException {
		KubeUtils client;
		Cluster cluster = clusterBiz.getClusterById(storage.getClusterId());
		try {
            client = Fabric8KubeUtils.buildKubeUtils(cluster, storage.getNamespace());
        } catch (K8sDriverException e) {
            throw new DriverException(e.getMessage());
        }
		
		// create pv
		NFSVolumeSource nfsVolumeSource = new NFSVolumeSource(storage.getPath(), false, storage.getServer());
		PersistentVolumeSpec persistentVolumeSpec = new PersistentVolumeSpec();
		persistentVolumeSpec.setNfs(nfsVolumeSource);
		Map<String, io.fabric8.kubernetes.api.model.Quantity> requests = new HashMap<String, io.fabric8.kubernetes.api.model.Quantity>();
		io.fabric8.kubernetes.api.model.Quantity quantity = new io.fabric8.kubernetes.api.model.Quantity("500", "Mi");
		requests.put("storage", quantity);
		List<String> accessModes = new ArrayList<String>();
		accessModes.add("ReadWriteMany");
		persistentVolumeSpec.setAccessModes(accessModes);
		persistentVolumeSpec.setCapacity(requests);
		Map<String, String> label = new HashMap<>();
        label.put("name", GlobalConstant.RC_NAME_PREFIX + storage.getName()+"-pv");
		PersistentVolume persistentVolume = new PersistentVolumeBuilder().withNewMetadata().withName(GlobalConstant.RC_NAME_PREFIX + storage.getName()+"-pv").withLabels(label).endMetadata().build();
		persistentVolume.setSpec(persistentVolumeSpec);
		client.createPersistentVolume(persistentVolume);
		
		// create pvc
		PersistentVolumeClaim persistentVolumeClaim = new PersistentVolumeClaimBuilder().withNewMetadata().withName(GlobalConstant.RC_NAME_PREFIX + storage.getName()+"-pvc").endMetadata().build();
		ResourceRequirements resourceRequirements = new ResourceRequirements();
		resourceRequirements.setRequests(requests);
		PersistentVolumeClaimSpec persistentVolumeClaimSpec = new PersistentVolumeClaimSpec();
		persistentVolumeClaimSpec.setResources(resourceRequirements);
		persistentVolumeClaimSpec.setAccessModes(accessModes);
		LabelSelector selector = new LabelSelector();
		selector.setMatchLabels(label);
		persistentVolumeClaimSpec.setSelector(selector);
		persistentVolumeClaim.setSpec(persistentVolumeClaimSpec);
		client.createPersistentVolumeClaim(persistentVolumeClaim);
	}
	
	private void deletePVAndPVC(int id) throws DriverException, K8sDriverException {
		Storage storage = storageBiz.getStorageById(id);
		KubeUtils client;
		Cluster cluster = clusterBiz.getClusterById(storage.getClusterId());
		try {
            client = Fabric8KubeUtils.buildKubeUtils(cluster, storage.getNamespace());
        } catch (K8sDriverException e) {
            throw new DriverException(e.getMessage());
        }
		client.deletePersistentVolumeClaim(GlobalConstant.RC_NAME_PREFIX + storage.getName()+"-pvc");
		client.deletePersistentVolume(GlobalConstant.RC_NAME_PREFIX + storage.getName()+"-pv");
	}
	
	private void updatePVAndPVC(Storage storage) throws DriverException, K8sDriverException {
		deletePVAndPVC(storage.getId());
		createPVAndPVC(storage);
	}

	@Override
	public HttpResponseTemp<?> listStorageByClusterId(int clusterId) {
		int userId = CurrentThreadInfo.getUserId();
		Boolean isAdmin = AuthUtil.isAdmin(userId);
		List<CollectionAuthorityMap> authorityMaps = AuthUtil.getCollectionList(userId, resourceType);
		List<StorageInfo> storageInfoListByClusterId = new ArrayList<StorageInfo>();
		for(CollectionAuthorityMap authorityMap: authorityMaps) {
			Storage storage = storageBiz.getStorageById(authorityMap.getCollectionId());
			if(clusterId == storage.getClusterId()) {
				StorageInfo storageInfo = generateStorageInfo(authorityMap, isAdmin);
				if(storageInfo != null) {
					storageInfoListByClusterId.add(generateStorageInfo(authorityMap, isAdmin));
				}
			}	
		}
		return ResultStat.OK.wrap(storageInfoListByClusterId);
	}
	
	private StorageInfo generateStorageInfo(CollectionAuthorityMap authorityMap, Boolean isAdmin) {
		Storage storage = storageBiz.getStorageById(authorityMap.getCollectionId());
		if(storage == null) {
			return null;
		}
		StorageInfo storageInfo = new StorageInfo(storage);
		String userName = AuthUtil.getUserNameById(storageInfo.getCreatorId());
		storageInfo.setCreatorName(userName);
		List<CollectionAuthorityMap> authorityMaps = collectionBiz.getAuthoritiesByCollectionIdAndResourceType(storageInfo.getId(), resourceType);
        if (authorityMaps == null) {
        	storageInfo.setMemberCount(0);
        } else {
        	storageInfo.setMemberCount(authorityMaps.size());
        }
        if (isAdmin) {
        	storageInfo.setRole(Role.MASTER);
        } else {
        	storageInfo.setRole(authorityMap.getRole());
        }
		return storageInfo;
	}
	
	@Override
	public HttpResponseTemp<?> deleteStorage(int id) {
		int userId = CurrentThreadInfo.getUserId();
        AuthUtil.collectionVerify(userId, id, resourceType, OperationType.DELETE, -1);
        List<CollectionResourceMap> collectionResourceMaps = collectionBiz.getResourcesByCollectionIdAndResourceType(
                id, ResourceType.DEPLOY);
        if (collectionResourceMaps != null && collectionResourceMaps.size() > 0) {
            throw ApiException.wrapMessage(ResultStat.CANNOT_DELETE_DEPLOY_COLLECTION, "You cannot delete a deploy collection" +
                    "with deploy exists");
        }
        try {
			deletePVAndPVC(id);
		} catch (DriverException | K8sDriverException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        storageBiz.deleteStorage(id);
        collectionBiz.deleteAuthoritiesByCollectionIdAndResourceType(id, resourceType);
        operationHistory.insertRecord(new OperationRecord(
                id,
                resourceType,
                OperationType.DELETE,
                CurrentThreadInfo.getUserId(),
                CurrentThreadInfo.getUserName(),
                "OK",
                "",
                System.currentTimeMillis()
        ));
		storageBiz.deleteStorage(id);
		return ResultStat.OK.wrap("删除成功");
	}

	@Override
	public HttpResponseTemp<?> updateStorage(Storage storage) {
		Storage oldStorage = storageBiz.getStorageById(storage.getId());
		if(!oldStorage.getName().equals(storage.getName())) {
			Storage isExist = storageBiz.getStorageByName(storage.getName());
			if(isExist != null) {
				return ResultStat.PARAM_ERROR.wrap("名称已经存在");
			}
		}
		
		if(!oldStorage.getPath().equals(storage.getPath()) || !oldStorage.getServer().equals(storage.getServer())) {
			List<Storage> error = storageBiz.getStorageByPathAndServer(storage.getPath(), storage.getServer());
			if(error != null && error.size() > 0) {
				return ResultStat.PARAM_ERROR.wrap("路径和服务器地址重复");
			}
		}
		storageBiz.updateStorage(storage);
		try {
			updatePVAndPVC(storage);
		} catch (DriverException | K8sDriverException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		operationHistory.insertRecord(new OperationRecord(
				storage.getCreatorId(),
                resourceType,
                OperationType.MODIFY,
                CurrentThreadInfo.getUserId(),
                CurrentThreadInfo.getUserName(),
                "OK",
                "",
                System.currentTimeMillis()
        ));
		return ResultStat.OK.wrap("更新成功");
	}

	@Override
	public HttpResponseTemp<?> getStorageById(int id) {
		Storage storage = storageBiz.getStorageById(id);
		return ResultStat.OK.wrap(storage);
	}

	@Override
	public HttpResponseTemp<?> getStorageByClusterAndNamespace(int clusterId, String namespace) {
		int userId = CurrentThreadInfo.getUserId();
		Boolean isAdmin = AuthUtil.isAdmin(userId);
		List<CollectionAuthorityMap> authorityMaps = AuthUtil.getCollectionList(userId, resourceType);
		List<StorageInfo> storageInfoListByClusterId = new ArrayList<StorageInfo>();
		for(CollectionAuthorityMap authorityMap: authorityMaps) {
			Storage storage = storageBiz.getStorageById(authorityMap.getCollectionId());
			if(clusterId == storage.getClusterId() && namespace.equals(storage.getNamespace())) {
				StorageInfo storageInfo = generateStorageInfo(authorityMap, isAdmin);
				if(storageInfo != null) {
					storageInfoListByClusterId.add(generateStorageInfo(authorityMap, isAdmin));
				}
			}
			
		}
		return ResultStat.OK.wrap(storageInfoListByClusterId);
	}

	@Override
	public HttpResponseTemp<?> getCountDeployById(int id) {
		return ResultStat.OK.wrap(storageBiz.getCountDeployById(id));
	}

	@Override
	public HttpResponseTemp<?> getDeployInstanceById(int id) {
		List<VolumeDeployMap> volumeDeployMapList = storageBiz.getDeployInstanceById(id);
		List<DeployInstance> deployInstanceList = new ArrayList<DeployInstance>();
		for(VolumeDeployMap vdm : volumeDeployMapList) {
			Deployment deployment = deploymentBiz.getById("deployment", vdm.getDeployId(), Deployment.class);
			Version version = versionBiz.getById("version", vdm.getVersionId(), Version.class);
			DeployInstance deployInstance = new DeployInstance();
			deployInstance.setDeployName(deployment.getName());
			deployInstance.setVersionName(version.getName());
			deployInstance.setNamespace(deployment.getNamespace());
			String clusterName = clusterBiz.getClusterById(deployment.getClusterId()).getName();
			deployInstance.setClusterName(clusterName);
			deployInstanceList.add(deployInstance);
		}
		return ResultStat.OK.wrap(deployInstanceList);
	}
	
	@Override
	public HttpResponseTemp<?> sreSetStorage(User user, Storage storage) {
		if(storage == null) {
			return ResultStat.PARAM_ERROR.wrap("参数不能为空");
		}
		
		List<Storage> error = storageBiz.getStorageByPathAndServer(storage.getPath(), storage.getServer());
		if(error != null && error.size() > 0) {
			return ResultStat.PARAM_ERROR.wrap("路径和服务器地址重复");
		}
		storage.setCreateTime(System.currentTimeMillis());
		storageBiz.createStorage(storage);
		try {
			createPVAndPVC(storage);
		} catch (DriverException | K8sDriverException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		CollectionAuthorityMap collectionAuthorityMap = new CollectionAuthorityMap(storage.getId(),
                resourceType,
                user.getId(),
                Role.MASTER,
                System.currentTimeMillis()
                );
        collectionBiz.addAuthority(collectionAuthorityMap);
        operationHistory.insertRecord(new OperationRecord(
        		storage.getId(),
                resourceType,
                OperationType.SET,
                user.getId(),
                user.getUsername(),
                "OK",
                "",
                System.currentTimeMillis()
        ));
		return ResultStat.OK.wrap(storage);
	}
	
	@Override
	public HttpResponseTemp<?> sreDeleteStorage(User user, int id) {
        AuthUtil.collectionVerify(user.getId(), id, resourceType, OperationType.DELETE, -1);
        int numberOfDeploy = storageBiz.getCountDeployById(id);
        if (numberOfDeploy > 0) {
            throw ApiException.wrapMessage(ResultStat.CANNOT_DELETE_DEPLOY_COLLECTION, "You cannot delete a storage" +
                    "with deploy exists");
        }
        try {
			deletePVAndPVC(id);
		} catch (DriverException | K8sDriverException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        storageBiz.deleteStorage(id);
        collectionBiz.deleteAuthoritiesByCollectionIdAndResourceType(id, resourceType);
        operationHistory.insertRecord(new OperationRecord(
                id,
                resourceType,
                OperationType.DELETE,
                user.getId(),
                user.getUsername(),
                "OK",
                "",
                System.currentTimeMillis()
        ));
		storageBiz.deleteStorage(id);
		return ResultStat.OK.wrap("删除成功");
	}

	@Override
	public HttpResponseTemp<?> sregetPVCNameById(int id) {
		Storage storage = storageBiz.getStorageById(id);
		String pvcName = storage.getName() + "-pvc";
		return ResultStat.OK.wrap(pvcName);
	}
	
}
